package cn.edu.fudan.dsm.MultiIndexes.operator.file;

import cn.edu.fudan.dsm.MultiIndexes.common.entity.TimeSeriesNode;
import cn.edu.fudan.dsm.MultiIndexes.common.entity.rowkey.TimeSeriesRowKey;
import cn.edu.fudan.dsm.MultiIndexes.operator.TimeSeriesOperator;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class TimeSeriesFileOperator implements TimeSeriesOperator {

    private long fileLength;
    private long seriesLength;
    private String path;
    private FileHandler fileHandler;

    public TimeSeriesFileOperator(String filepath, long fileLength, long seriesLength, boolean rebuild) throws IOException {
        this.path = filepath;
        this.fileLength = fileLength;
        this.seriesLength = seriesLength;
        fileHandler = new FileHandler(path, rebuild ? "w" : "r");
    }

    @Override
    public List<Double> readTimeSeries(long left, int length) throws IOException {
        int data_size = 8;
        List<Double> ret = new ArrayList<>();
        byte[] byteData = fileHandler.read(left * data_size, length * data_size);
        for(int  i = 0; i < length; i ++) {
            byte[] tb = new byte[data_size];
            System.arraycopy(byteData,i*data_size, tb,0,data_size);
            ret.add(new BigDecimal(String.valueOf(bytes2Double(tb))).doubleValue());
        }
        return ret;
    }

    @Override
    public TimeSeriesNodeIterator readAllTimeSeries() throws IOException {
        return null;
    }

    @Override
    public void writeTimeSeriesNode(TimeSeriesRowKey rowKey, TimeSeriesNode node) throws IOException {
        fileHandler.write(node.toBytes());
    }

    @Override
    public List<List<Double>> readAllTimeSeries(long offset, long length, long readLength) throws IOException {
        int data_size = 8;
        long totalLength = offset + readLength;

        List<List<Double>> getData = new ArrayList<>();
        byte[] byteData = new byte[1024 * data_size];
        long pos = offset;
        int tmpListC = 0;
        List<Double> tmpList = new ArrayList<>();
        while(pos < totalLength){
            byteData = fileHandler.read(pos * data_size, (int) Math.min((totalLength - pos) * data_size, byteData.length));
            for(int i = 0; i < Math.min(totalLength - pos, byteData.length / data_size); i ++){
                byte[] tb = new byte[data_size];
                System.arraycopy(byteData,i * data_size,tb,0,data_size);
                tmpList.add(new BigDecimal(String.valueOf(bytes2Double(tb))).doubleValue());
                tmpListC ++;
                if(tmpListC == length){
                    List<Double> addList = new ArrayList<>();
                    addList.addAll(tmpList);
                    getData.add(addList);
                    tmpList.clear();
                    tmpListC = 0;
                }
            }
            pos += 1024;
        }
        if(tmpListC != 0)getData.add(tmpList);

        return getData;
    }

    @Override
    public Double TsDist(List<Double> ts, List<Double> queryData) {
        Double dist = 0.0;
        for(int i = 0; i < ts.size(); i++)dist += (ts.get(i) - queryData.get(i)) * (ts.get(i) - queryData.get(i));
        return Math.sqrt(dist);
    }

    @Override
    public Double TsNormED(List<Double> tss, List<Double> queryData, double alpha, double beta) {
        Double dist = 0.0;
        int tsLen = queryData.size();
        float ext = 0;
        float ex2t = 0;
        float exq = 0;
        float ex2q = 0;
        for(int i = 0; i < tsLen; i ++){
            ext += tss.get(i);
            exq += queryData.get(i);
            ex2t += tss.get(i) * tss.get(i);
            ex2q += queryData.get(i) * queryData.get(i);
        }
        ext /= tsLen;
        ex2t = (float)Math.sqrt(ex2t / tsLen - ext * ext);

        exq /= tsLen;
        ex2q = (float)Math.sqrt(ex2q / tsLen - exq * exq);

        for(int i =0; i < tsLen; i++){
            dist += (((tss.get(i) - ext) / ex2t) - ((queryData.get(i) - exq) / ex2q)) * (((tss.get(i) - ext) / ex2t) - ((queryData.get(i) - exq) / ex2q));
        }
        return Math.sqrt(dist);
    }

    @Override
    public void close() throws IOException {
        fileHandler.close();
    }
    public double bytes2Double(byte[] arr) {
        long value = 0;
        for (int i = 7; i >= 0; i--) {
            value |= ((long) (arr[i] & 0xff)) << (8 * (7 -  i));
        }
        return Double.longBitsToDouble(value);
    }

    public float bytes2Float(byte[] arr){
        int value = 0;
        for (int i = 3; i >= 0; i--) {
            value |= ((long) (arr[i] & 0xff)) << (8 * (3 -  i));
        }
        return Float.intBitsToFloat(value);
    }
}